package org.basis.enhance.async.init.processor;

import org.basis.enhance.async.init.annotation.BasisAsyncInit;
import org.basis.enhance.async.init.exception.BasisAsyncInitException;
import org.basis.enhance.async.init.registy.AsyncInitBeanRegistry;
import org.basis.enhance.async.init.utils.BeanDefinitionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.StandardMethodMetadata;
import org.springframework.lang.NonNull;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 异步初始化beanFactory后置处理器
 *
 * @author wenpan 2023/06/11 15:50
 */
public class AsyncInitBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    private final static Logger logger = LoggerFactory.getLogger(AsyncInitBeanFactoryPostProcessor.class);

    @Override
    public void postProcessBeanFactory(@NonNull ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // 获取beanFactory中所有的 BeanDefinitionNames
        Arrays.stream(beanFactory.getBeanDefinitionNames())
                // 以name为key, BeanDefinition 为value构造集合
                .collect(Collectors.toMap(Function.identity(), beanFactory::getBeanDefinition))
                // 对集合中每个元素都执行 scanAsyncInitBeanDefinition 方法
                .forEach((key, value) -> scanAsyncInitBeanDefinition(key, value, beanFactory));
    }

    /**
     * {@link ScannedGenericBeanDefinition}
     * {@link AnnotatedGenericBeanDefinition}
     * {@link GenericBeanDefinition}
     * {@link org.springframework.beans.factory.support.ChildBeanDefinition}
     * {@link org.springframework.beans.factory.support.RootBeanDefinition}
     */
    private void scanAsyncInitBeanDefinition(String beanId,
                                             BeanDefinition beanDefinition,
                                             ConfigurableListableBeanFactory beanFactory) {
        // 该bean定义信息是否来源于配置类导入到beanFactory，比如：通过@Import、@Configuration 等注解导入的类
        if (BeanDefinitionUtil.isFromConfigurationSource(beanDefinition)) {
            // 如果是通过配置类导入的，则解析导入该beanDefinition的配置类的每个方法
            scanAsyncInitBeanDefinitionOnMethod(beanId, (AnnotatedBeanDefinition) beanDefinition);
        } else {
            Class<?> beanClassType = BeanDefinitionUtil.resolveBeanClassType(beanDefinition);
            if (beanClassType == null) {
                logger.warn("Bean class type cant be resolved from bean of {}", beanId);
                return;
            }
            // BasisAsyncInit 注解直接标注在Class上（比如：使用 @ComponentScan 等导入到beanFactory的，则直接扫描该class）
            scanAsyncInitBeanDefinitionOnClass(beanId, beanClassType, beanDefinition, beanFactory);
        }
    }

    /**
     * 根据bean工厂方法，扫描异步初始化的bean定义信息
     *
     * @param beanId         beanId
     * @param beanDefinition bean定义信息
     */
    private void scanAsyncInitBeanDefinitionOnMethod(String beanId, AnnotatedBeanDefinition beanDefinition) {
        Class<?> returnType;
        Class<?> declaringClass;
        // 候选的方法
        List<Method> candidateMethods = new ArrayList<>();
        // 通过bean定义信息获取工厂方法元数据（什么是工厂方法？比如配置类中的使用@Bean标注的方法就是一个工厂方法，用于创建bean）
        // MethodMetadata 它是 Bean 工厂方法的元数据信息，包含了工厂方法的各种元数据，如方法名、返回类型、参数类型等。
        MethodMetadata methodMetadata = beanDefinition.getFactoryMethodMetadata();
        try {
            // 通过工厂方法元数据，获取工厂方法的返回值（也就是要注入的bean的类型）
            assert methodMetadata != null;
            returnType = ClassUtils.forName(methodMetadata.getReturnTypeName(), null);
            // 通过类名称加载该类的Class（从工厂方法元数据里获取到该工厂方法属于哪个类，然后加载该类的Class）
            declaringClass = ClassUtils.forName(methodMetadata.getDeclaringClassName(), null);
        } catch (Throwable throwable) {
            logger.error("resolve returnType and declaringClass error.", throwable);
            return;
        }
        // 在 Spring 中，如果 Bean 工厂方法是通过 Java 配置类或 XML 配置文件中的 <bean> 标签来定义的，那么 getFactoryMethodMetadata() 方法
        // 返回的是 StandardMethodMetadata 类型的对象，该对象提供了更详细的方法元数据信息。而如果 Bean 工厂方法是通过 @Bean 注解在配置类中定义的，
        // 那么 getFactoryMethodMetadata() 方法返回的就是 MethodMetadata 接口的默认实现类 SimpleMethodMetadata 的实例，该实例提供的方法元数据信息相对简单
        if (methodMetadata instanceof StandardMethodMetadata) {
            // 如果是通过xml配置注入的bean，那么methodMetadata类型为StandardMethodMetadata
            // 获取当前bean工厂方法对应的Method对象，所以直接通过getIntrospectedMethod方法即可获取到工厂方法
            candidateMethods.add(((StandardMethodMetadata) methodMetadata).getIntrospectedMethod());
        } else {
            // 遍历bean工厂方法所属的类的每个方法
            for (Method m : declaringClass.getDeclaredMethods()) {
                // 如果方法名称或方法返回值和bean工厂方法不一样，则跳过该方法
                if (!m.getName().equals(methodMetadata.getMethodName())
                        || !m.getReturnType().getTypeName().equals(methodMetadata.getReturnTypeName())) {
                    continue;
                }

                // 如果该方法上没有标注 Bean 注解，则跳过
                if (!AnnotatedElementUtils.hasAnnotation(m, Bean.class)) {
                    continue;
                }

                Bean annotation = m.getAnnotation(Bean.class);
                // 可能的BeanName
                Set<String> beanNames = new HashSet<>();
                // 方法名有可能作为注入容器的beanName
                beanNames.add(m.getName());
                if (Objects.nonNull(annotation)) {
                    // @Bean注解的value和name可能作为beanName
                    beanNames.addAll(Arrays.asList(annotation.value()));
                    beanNames.addAll(Arrays.asList(annotation.name()));
                }
                // 如果该bean工厂方法所有可能的 beanNames 都不等于 beanId 则说明该method不是bean工厂方法，需跳过
                if (!beanNames.contains(beanId)) {
                    continue;
                }

                // 将该方法添加到候选方法列表中
                candidateMethods.add(m);
            }

            // 如果从bean工厂方法所在的类中只找到一个候选方法
            if (candidateMethods.size() == 1) {
                // 获取该候选方法上的 BasisAsyncInit 注解
                BasisAsyncInit annotation = candidateMethods.get(0).getAnnotation(BasisAsyncInit.class);
                if (annotation == null) {
                    // 如果从方法上没有找到 BasisAsyncInit 注解，则从方法返回Class上查找一下
                    annotation = returnType.getAnnotation(BasisAsyncInit.class);
                }
                registerAsyncInitBean(beanId, annotation, beanDefinition);
            } else if (candidateMethods.size() > 1) {
                // 如果列表中有多个方法，则判断是否存在 BasisAsyncInit 注解，如果有则抛出异常
                for (Method m : candidateMethods) {
                    // 方法上标有 BasisAsyncInit 注解
                    if (AnnotatedElementUtils.hasAnnotation(m, BasisAsyncInit.class)
                            // 返回Class上标注有 BasisAsyncInit 注解
                            || AnnotatedElementUtils.hasAnnotation(returnType, BasisAsyncInit.class)) {
                        throw new BasisAsyncInitException(String.format("%s has many candidateMethod.", declaringClass.getCanonicalName()));
                    }
                }
            }
            // candidateMethods.size == 0 则说明该bean工厂方法没有需要异步初始化的bean
        }

    }

    private void scanAsyncInitBeanDefinitionOnClass(String beanId,
                                                    Class<?> beanClass,
                                                    BeanDefinition beanDefinition,
                                                    ConfigurableListableBeanFactory beanFactory) {
        BasisAsyncInit basisAsyncInitAnnotation = AnnotationUtils.findAnnotation(beanClass, BasisAsyncInit.class);
        registerAsyncInitBean(beanId, basisAsyncInitAnnotation, beanDefinition);
    }

    /**
     * 注册需要异步初始化的bean
     *
     * @param beanId         beanId
     * @param annotation     BasisAsyncInit主耳机
     * @param beanDefinition 异步初始化的bean定义信息
     */
    private void registerAsyncInitBean(String beanId, BasisAsyncInit annotation, BeanDefinition beanDefinition) {
        // 不存在BasisAsyncInit注解，或者注解的value为false时不注册
        if (annotation == null || !annotation.value()) {
            return;
        }
        AsyncInitBeanRegistry.registerAsyncInitBean(beanId, beanDefinition.getInitMethodName());
    }

}
